///*******************************************************************************
// * Copyright (c) 2015, 2016 EfficiOS Inc., Alexandre Montplaisir
// *
// * All rights reserved. This program and the accompanying materials are
// * made available under the terms of the Eclipse Public License v1.0 which
// * accompanies this distribution, and is available at
// * http://www.eclipse.org/legal/epl-v10.html
// *******************************************************************************/
//
//package org.lttng.scope.lami.core.module;
//
//import static java.util.Objects.requireNonNull;
//import static org.lttng.scope.common.core.NonNullUtils.checkNotNullContents;
//import static org.lttng.scope.common.core.NonNullUtils.nullToEmptyString;
//
//import java.io.File;
//import java.nio.file.Files;
//import java.nio.file.Paths;
//import java.util.ArrayList;
//import java.util.Arrays;
//import java.util.Collection;
//import java.util.Collections;
//import java.util.EnumSet;
//import java.util.List;
//import java.util.Map;
//import java.util.StringJoiner;
//import java.util.WeakHashMap;
//import java.util.function.Predicate;
//import java.util.logging.Logger;
//import java.util.regex.Pattern;
//import java.util.stream.Stream;
//
//import org.eclipse.core.runtime.CoreException;
//import org.eclipse.core.runtime.IProgressMonitor;
//import org.eclipse.core.runtime.IStatus;
//import org.eclipse.core.runtime.Status;
//import org.eclipse.jdt.annotation.NonNull;
//import org.eclipse.jdt.annotation.Nullable;
//import org.eclipse.tracecompass.tmf.core.analysis.ondemand.IOnDemandAnalysis;
//import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimeRange;
//import org.eclipse.tracecompass.tmf.core.trace.ITmfTrace;
//import org.json.JSONArray;
//import org.json.JSONException;
//import org.json.JSONObject;
//import org.lttng.scope.common.core.log.TraceCompassLog;
//import org.lttng.scope.common.core.process.ProcessUtils;
//import org.lttng.scope.common.core.process.ProcessUtils.OutputReaderFunction;
//import org.lttng.scope.lami.core.LamiStrings;
//import org.lttng.scope.lami.core.activator.internal.Activator;
//import org.lttng.scope.lami.core.aspect.LamiDurationAspect;
//import org.lttng.scope.lami.core.aspect.LamiEmptyAspect;
//import org.lttng.scope.lami.core.aspect.LamiGenericAspect;
//import org.lttng.scope.lami.core.aspect.LamiIRQNameAspect;
//import org.lttng.scope.lami.core.aspect.LamiIRQNumberAspect;
//import org.lttng.scope.lami.core.aspect.LamiIRQTypeAspect;
//import org.lttng.scope.lami.core.aspect.LamiMixedAspect;
//import org.lttng.scope.lami.core.aspect.LamiProcessNameAspect;
//import org.lttng.scope.lami.core.aspect.LamiProcessPIDAspect;
//import org.lttng.scope.lami.core.aspect.LamiProcessTIDAspect;
//import org.lttng.scope.lami.core.aspect.LamiTableEntryAspect;
//import org.lttng.scope.lami.core.aspect.LamiTimeRangeBeginAspect;
//import org.lttng.scope.lami.core.aspect.LamiTimeRangeDurationAspect;
//import org.lttng.scope.lami.core.aspect.LamiTimeRangeEndAspect;
//import org.lttng.scope.lami.core.aspect.LamiTimestampAspect;
//import org.lttng.scope.lami.core.types.LamiData;
//import org.lttng.scope.lami.core.types.LamiData.DataType;
//import org.lttng.scope.lami.core.types.LamiTimeRange;
//
//import com.google.common.annotations.VisibleForTesting;
//import com.google.common.collect.ImmutableList;
//import com.google.common.collect.ImmutableMap;
//import com.google.common.collect.ImmutableMultimap;
//import com.google.common.collect.Multimap;
//
///**
// * Base class for analysis modules that call external scripts implementing the
// * LAMI protocol.
// *
// * @author Alexandre Montplaisir
// * @author Philippe Proulx
// */
//public class LamiAnalysis implements IOnDemandAnalysis {
//
//    private static final Logger LOGGER = TraceCompassLog.getLogger(LamiAnalysis.class);
//    private static final String DOUBLE_QUOTES = "\""; //$NON-NLS-1$
//    private static final String SPACE = " "; //$NON-NLS-1$
//
//    private static final String BASH_EXECUTABLE = "bash"; //$NON-NLS-1$
//    private static final String BASH_FLAG = "-c"; //$NON-NLS-1$
//
//    /* Flags passed to the analysis scripts */
//    private static final String MI_VERSION_FLAG = "--mi-version"; //$NON-NLS-1$
//    private static final String TEST_COMPATIBILITY_FLAG = "--test-compatibility"; //$NON-NLS-1$
//    private static final String METADATA_FLAG = "--metadata"; //$NON-NLS-1$
//    private static final String PROGRESS_FLAG = "--output-progress"; //$NON-NLS-1$
//    private static final String BEGIN_FLAG = "--begin"; //$NON-NLS-1$
//    private static final String END_FLAG = "--end"; //$NON-NLS-1$
//
//    /* Log messages */
//    private static final String LOG_RUNNING_MESSAGE = "[LamiAnalysis:RunningCommand] "; //$NON-NLS-1$
//    private static final String LOG_NO_MI_VERSION_FMT = "[LamiAnalysis:InvalidMIVersionReport] Command \"%s\" reports no specific or invalid MI version"; //$NON-NLS-1$
//
//    /* Tokens of the complete command */
//    private final List<String> fScriptCommand;
//
//    /* 0 means pre-1.0 LAMI protocol */
//    private int fMiVersion = 0;
//
//    /* Custom name given to this analysis. */
//    private final String fName;
//
//    /*
//     * True if this analysis is defined by a user, that is, not
//     * provided by TC.
//     */
//    private final boolean fIsUserDefined;
//
//    /*
//     * The predicate to use to determine if a given trace applies
//     * to this analysis, that is, the given trace's type is compatible
//     * with this analysis a priori. The analysis might not be able to
//     * execute for a given trace, but this is returned by canExecute().
//     */
//    private final Predicate<ITmfTrace> fAppliesTo;
//
//    /* Data defined by the analysis's metadata */
//    private @Nullable String fAnalysisTitle;
//    private @Nullable Map<String, LamiTableClass> fTableClasses;
//
//    /* Cache: true if the initialization process took place already. */
//    private boolean fInitialized = false;
//
//    /*
//     * Cache: assigns a boolean to a trace which indicates if this analysis can
//     * be executed on the given trace. Presence in the map indicates the
//     * compatibility test has already run, so "false" is different than "absent"
//     * here.
//     *
//     * Uses weak keys, so we do not hold references to trace objects and prevent
//     * them from being disposed.
//     */
//    private final Map<ITmfTrace, Boolean> fTraceCompatibilityCache = new WeakHashMap<>();
//
//    /**
//     * Available features.
//     */
//    private enum Features {
//        /** The MI version of the analysis is supported. */
//        VERSION_IS_SUPPORTED,
//
//        /** The analysis is supported at all. */
//        SUPPORTED,
//
//        /** The analysis supports a progress indication output. */
//        OUTPUT_PROGRESS,
//
//        /** The analysis supports testing a given trace for compatibility. */
//        TEST_COMPATIBILITY
//    }
//
//    /* Available features depending on the MI version */
//    private final EnumSet<Features> fFeatures = requireNonNull(EnumSet.noneOf(Features.class));
//
//    /**
//     * Constructor. To be called by implementing classes.
//     *
//     * @param name
//     *            Name of this analysis
//     * @param isUserDefined
//     *            {@code true} if this is a user-defined analysis
//     * @param appliesTo
//     *            Predicate to use to check whether or not this analysis applies
//     *            to a given trace
//     * @param args
//     *            Analysis arguments, including the executable name (first
//     *            argument)
//     */
//    public LamiAnalysis(String name, boolean isUserDefined, Predicate<ITmfTrace> appliesTo,
//            List<String> args) {
//        fScriptCommand = ImmutableList.copyOf(args);
//        fName = name;
//        fIsUserDefined = isUserDefined;
//        fAppliesTo = appliesTo;
//    }
//
//    /**
//     * Map of pre-defined charts, for every table class names.
//     *
//     * If a table class is not in this map then it means that table has no
//     * predefined charts.
//     *
//     * @return The chart models, per table class names
//     */
//    protected Multimap<String, LamiChartModel> getPredefinedCharts() {
//        return ImmutableMultimap.of();
//    }
//
//    @Override
//    public final boolean appliesTo(ITmfTrace trace) {
//        return fAppliesTo.test(trace);
//    }
//
//    private boolean testCompatibility(ITmfTrace trace) {
//        final @NonNull String tracePath = requireNonNull(trace.getPath());
//
//        StringJoiner sj = new StringJoiner(SPACE);
//        fScriptCommand.forEach(sj::add);
//        sj.add(TEST_COMPATIBILITY_FLAG);
//        sj.add(tracePath);
//        String command = sj.toString();
//
//        final boolean isCompatible = (getOutputFromCommand(command) != null);
//
//        /* Add this result to the compatibility cache. */
//        fTraceCompatibilityCache.put(trace, isCompatible);
//
//        return isCompatible;
//    }
//
//    private boolean isSupported() {
//        initialize();
//
//        return fFeatures.contains(Features.SUPPORTED);
//    }
//
//    @Override
//    public boolean canExecute(ITmfTrace trace) {
//        /* Make sure this analysis is supported at all. */
//        if (!isSupported()) {
//            return false;
//        }
//
//        if (!fFeatures.contains(Features.TEST_COMPATIBILITY)) {
//            /*
//             * No support for dynamic compatibility testing: suppose this
//             * analysis can run on any trace.
//             */
//            return true;
//        }
//
//        /* Check if this trace is already registered in the cache. */
//        if (fTraceCompatibilityCache.getOrDefault(trace, false)) {
//            return true;
//        }
//
//        /*
//         * Test compatibility since it's supported.
//         */
//        return testCompatibility(trace);
//    }
//
//    private void setFeatures() {
//        if (fMiVersion == 0) {
//            // Pre-1.0 LAMI protocol: supported for backward compatibility
//            fFeatures.add(Features.VERSION_IS_SUPPORTED);
//            return;
//        }
//
//        if (fMiVersion >= 100 && fMiVersion < 200) {
//            // LAMI 1.x
//            fFeatures.add(Features.VERSION_IS_SUPPORTED);
//            fFeatures.add(Features.OUTPUT_PROGRESS);
//            fFeatures.add(Features.TEST_COMPATIBILITY);
//        }
//    }
//
//    private void readVersion() {
//        String command = new StringJoiner(SPACE)
//                .add(fScriptCommand.get(0))
//                .add(MI_VERSION_FLAG)
//                .toString();
//
//        final String output = getOutputFromCommand(command);
//
//        if (output == null) {
//            LOGGER.info(() -> String.format(LOG_NO_MI_VERSION_FMT, command));
//            return;
//        }
//
//        final String versionString = output.trim();
//
//        if (!versionString.matches("\\d{1,3}\\.\\d{1,3}")) { //$NON-NLS-1$
//            LOGGER.info(() -> String.format(LOG_NO_MI_VERSION_FMT, command));
//            return;
//        }
//
//        LOGGER.info(() -> String.format("[LamiAnalysis:MIVersionReport] Command \"%s\" reports MI version %s", //$NON-NLS-1$
//                command, versionString));
//
//        final String[] parts = versionString.split("\\."); //$NON-NLS-1$
//        final int major = Integer.valueOf(parts[0]);
//        final int minor = Integer.valueOf(parts[1]);
//
//        fMiVersion = major * 100 + minor;
//    }
//
//    private static boolean executableExists(String name) {
//        if (name.contains(File.separator)) {
//            /* This seems like a path, not just an executable name */
//            return Files.isExecutable(Paths.get(name));
//        }
//
//        /* Check if this name is found in the PATH environment variable */
//        final String pathEnv = System.getenv("PATH"); //$NON-NLS-1$
//        final String[] exeDirs = pathEnv.split(requireNonNull(Pattern.quote(File.pathSeparator)));
//
//        return Stream.of(exeDirs)
//                .map(Paths::get)
//                .anyMatch(path -> Files.isExecutable(path.resolve(name)));
//    }
//
//    /**
//     * Perform initialization of the LAMI script. This means verifying that it
//     * is actually present on disk, and that it returns correct --metadata.
//     */
//    @VisibleForTesting
//    protected synchronized void initialize() {
//        if (fInitialized) {
//            /* Already initialized */
//            return;
//        }
//
//        fInitialized = true;
//
//        /* Step 1: Check if the script's expected executable is on the PATH. */
//        final String executable = fScriptCommand.get(0);
//        final boolean executableExists = executableExists(executable);
//
//        if (!executableExists) {
//            /* Script is not found */
//            return;
//        }
//
//        /*
//         * Step 2: Read the version, which also gives us an indication as
//         * to whether or not this executable supports LAMI 1.0.
//         */
//        readVersion();
//
//        /*
//         * Set the available features according to the MI version.
//         */
//        setFeatures();
//
//        if (!fFeatures.contains(Features.VERSION_IS_SUPPORTED)) {
//            /* Unsupported LAMI version */
//            return;
//        }
//
//        /*
//         * Step 3: Check the metadata. This determines if the analysis is
//         * supported at all or not.
//         */
//        if (checkMetadata()) {
//            fFeatures.add(Features.SUPPORTED);
//        }
//    }
//
//    /**
//     * Verify that this script returns valid metadata.
//     *
//     * @return True if the command outputs a valid metadata object
//     */
//    @VisibleForTesting
//    protected boolean checkMetadata() {
//        /*
//         * The initialize() phase of the analysis will be used to check the
//         * script's metadata. Actual runs of the script will use the execute()
//         * method.
//         */
//        StringJoiner sj = new StringJoiner(SPACE);
//        fScriptCommand.forEach(sj::add);
//        sj.add(METADATA_FLAG);
//        String command = sj.toString();
//
//        LOGGER.info(() -> "[LamiAnalysis:RunningMetadataCommand] " + command.toString()); //$NON-NLS-1$
//        String output = getOutputFromCommand(command);
//        if (output == null || output.isEmpty()) {
//            return false;
//        }
//
//        /*
//         *
//         * Metadata should look this this:
//         *
//         * {
//         *   "version": [1, 5, 2, "dev"],
//         *   "title": "I/O latency statistics",
//         *   "authors": [
//         *     "Julien Desfossez",
//         *     "Antoine Busque"
//         *   ],
//         *   "description": "Provides statistics about the latency involved in various I/O operations.",
//         *   "url": "https://github.com/lttng/lttng-analyses",
//         *   "tags": [
//         *     "io",
//         *     "stats",
//         *     "linux-kernel",
//         *     "lttng-analyses"
//         *   ],
//         *   "table-classes": {
//         *     "syscall-latency": {
//         *       "title": "System calls latency statistics",
//         *       "column-descriptions": [
//         *         {"title": "System call", "type": "syscall"},
//         *         {"title": "Count", "type": "int", "unit": "operations"},
//         *         {"title": "Minimum duration", "type": "duration"},
//         *         {"title": "Average duration", "type": "duration"},
//         *         {"title": "Maximum duration", "type": "duration"},
//         *         {"title": "Standard deviation", "type": "duration"}
//         *       ]
//         *     },
//         *     "disk-latency": {
//         *       "title": "Disk latency statistics",
//         *       "column-descriptions": [
//         *         {"title": "Disk name", "type": "disk"},
//         *         {"title": "Count", "type": "int", "unit": "operations"},
//         *         {"title": "Minimum duration", "type": "duration"},
//         *         {"title": "Average duration", "type": "duration"},
//         *         {"title": "Maximum duration", "type": "duration"},
//         *         {"title": "Standard deviation", "type": "duration"}
//         *       ]
//         *     }
//         *   }
//         * }
//         *
//         */
//
//        try {
//            JSONObject obj = new JSONObject(output);
//            fAnalysisTitle = obj.getString(LamiStrings.TITLE);
//
//            JSONObject tableClasses = obj.getJSONObject(LamiStrings.TABLE_CLASSES);
//            @NonNull String[] tableClassNames = checkNotNullContents(JSONObject.getNames(tableClasses));
//
//            ImmutableMap.Builder<String, LamiTableClass> tablesBuilder = ImmutableMap.builder();
//            for (String tableClassName : tableClassNames) {
//                JSONObject tableClass = tableClasses.getJSONObject(tableClassName);
//
//                final String tableTitle = requireNonNull(tableClass.getString(LamiStrings.TITLE));
//                @NonNull JSONArray columnDescriptions = requireNonNull(tableClass.getJSONArray(LamiStrings.COLUMN_DESCRIPTIONS));
//
//                List<LamiTableEntryAspect> aspects = getAspectsFromColumnDescriptions(columnDescriptions);
//                Collection<LamiChartModel> chartModels = getPredefinedCharts().get(tableClassName);
//
//                tablesBuilder.put(tableClassName, new LamiTableClass(tableClassName, tableTitle, aspects, chartModels));
//            }
//
//            try {
//                fTableClasses = tablesBuilder.build();
//            } catch (IllegalArgumentException e) {
//                /*
//                 * This is thrown if there are duplicate keys in the map
//                 * builder.
//                 */
//                throw new JSONException("Duplicate table class entry in " + fAnalysisTitle); //$NON-NLS-1$
//            }
//
//        } catch (JSONException e) {
//            /* Error in the parsing of the JSON, script is broken? */
//            LOGGER.severe(() -> "[LamiAnalysis:ErrorParsingMetadata] msg=" + e.getMessage()); //$NON-NLS-1$
//            return false;
//        }
//        return true;
//    }
//
//    private static List<LamiTableEntryAspect> getAspectsFromColumnDescriptions(JSONArray columnDescriptions) throws JSONException {
//        ImmutableList.Builder<LamiTableEntryAspect> aspectsBuilder = new ImmutableList.Builder<>();
//        for (int j = 0; j < columnDescriptions.length(); j++) {
//            JSONObject column = columnDescriptions.getJSONObject(j);
//            DataType columnDataType;
//            String columnClass = column.optString(LamiStrings.CLASS, null);
//
//            if (columnClass == null) {
//                columnDataType = DataType.MIXED;
//            } else {
//                columnDataType = getDataTypeFromString(columnClass);
//            }
//
//            String columnTitle = column.optString(LamiStrings.TITLE, null);
//
//            if (columnTitle == null) {
//                columnTitle = String.format("%s #%d", columnDataType.getTitle(), j + 1); //$NON-NLS-1$
//            }
//
//            final int colIndex = j;
//            switch (columnDataType) {
//            case TIME_RANGE:
//                /*
//                 * We will add 3 aspects, to represent the start, end and
//                 * duration of this time range.
//                 */
//                aspectsBuilder.add(new LamiTimeRangeBeginAspect(columnTitle, colIndex));
//                aspectsBuilder.add(new LamiTimeRangeEndAspect(columnTitle, colIndex));
//                aspectsBuilder.add(new LamiTimeRangeDurationAspect(columnTitle, colIndex));
//                break;
//
//            case TIMESTAMP:
//                aspectsBuilder.add(new LamiTimestampAspect(columnTitle, colIndex));
//                break;
//
//            case PROCESS:
//                aspectsBuilder.add(new LamiProcessNameAspect(columnTitle, colIndex));
//                aspectsBuilder.add(new LamiProcessPIDAspect(columnTitle, colIndex));
//                aspectsBuilder.add(new LamiProcessTIDAspect(columnTitle, colIndex));
//                break;
//
//            case IRQ:
//                aspectsBuilder.add(new LamiIRQTypeAspect(columnTitle, colIndex));
//                aspectsBuilder.add(new LamiIRQNameAspect(columnTitle, colIndex));
//                aspectsBuilder.add(new LamiIRQNumberAspect(columnTitle, colIndex));
//                break;
//
//            case DURATION:
//                aspectsBuilder.add(new LamiDurationAspect(columnTitle, colIndex));
//                break;
//
//            case MIXED:
//                aspectsBuilder.add(new LamiMixedAspect(columnTitle, colIndex));
//                break;
//
//            // $CASES-OMITTED$
//            default:
//                String units = column.optString(LamiStrings.UNIT, null);
//
//                if (units == null) {
//                    units = columnDataType.getUnits();
//                }
//
//                /* We will add only one aspect representing the element */
//                LamiTableEntryAspect aspect = new LamiGenericAspect(columnTitle,
//                        units, colIndex, columnDataType.isContinuous(), false);
//                aspectsBuilder.add(aspect);
//                break;
//            }
//        }
//        /*
//         * SWT quirk : we need an empty column at the end or else the last data
//         * column will clamp to the right edge of the view if it is
//         * right-aligned.
//         */
//        aspectsBuilder.add(LamiEmptyAspect.INSTANCE);
//
//        return aspectsBuilder.build();
//    }
//
//    private static DataType getDataTypeFromString(String value) throws JSONException {
//        try {
//            return DataType.fromString(value);
//        } catch (IllegalArgumentException e) {
//            throw new JSONException("Unrecognized data type: " + value); //$NON-NLS-1$
//        }
//    }
//
//    /**
//     * Get the title of this analysis, as read from the script's metadata.
//     *
//     * @return The analysis title. Should not be null after the initialization
//     *         completed successfully.
//     */
//    public @Nullable String getAnalysisTitle() {
//        return fAnalysisTitle;
//    }
//
//    /**
//     * Get the result table classes defined by this analysis, as read from the
//     * script's metadata.
//     *
//     * @return The analysis' result table classes. Should not be null after the
//     *         execution completed successfully.
//     */
//    public @Nullable Map<String, LamiTableClass> getTableClasses() {
//        return fTableClasses;
//    }
//
//    /**
//     * Print the full command that will be run when calling {@link #execute},
//     * with the exception of the 'extraParams' that will be passed to execute().
//     *
//     * This can be used to display the command in the UI before it is actually
//     * run.
//     *
//     * @param trace
//     *            The trace on which to run the analysis
//     * @param range
//     *            The time range to specify. Null will not specify a time range,
//     *            which means the whole trace will be taken.
//     * @return The command as a single, space-separated string
//     */
//    public String getFullCommandAsString(ITmfTrace trace, @Nullable TmfTimeRange range) {
//        String tracePath = requireNonNull(trace.getPath());
//
//        ImmutableList.Builder<String> builder = getBaseCommand(range);
//        /*
//         * We can add double-quotes around the trace path, which could contain
//         * spaces, so that the resulting command can be easily copy-pasted into
//         * a shell.
//         */
//        builder.add(DOUBLE_QUOTES + tracePath + DOUBLE_QUOTES);
//        List<String> list = builder.build();
//        String ret = String.join(" ", list); //$NON-NLS-1$
//        return requireNonNull(ret);
//    }
//
//    /**
//     * Get the base part of the command that will be executed to run this
//     * analysis, supplying the given time range. Base part meaning:
//     *
//     * <pre>
//     * [script executable] [statically-defined parameters] [--begin/--end (if applicable)]
//     * </pre>
//     *
//     * Note that it does not include the path to the trace, that is to be added
//     * separately.
//     *
//     * @param range
//     *            The time range that will be passed
//     * @return The elements of the command
//     */
//    private ImmutableList.Builder<String> getBaseCommand(@Nullable TmfTimeRange range) {
//        long begin = 0;
//        long end = 0;
//        if (range != null) {
//            begin = range.getStartTime().getValue();
//            end = range.getEndTime().getValue();
//            if (range.getStartTime().compareTo(range.getEndTime()) > 0) {
//                begin = range.getEndTime().getValue();
//                end = range.getStartTime().getValue();
//            }
//        }
//
//        ImmutableList.Builder<String> builder = ImmutableList.builder();
//        builder.addAll(fScriptCommand);
//
//        if (fFeatures.contains(Features.OUTPUT_PROGRESS)) {
//            builder.add(PROGRESS_FLAG);
//        }
//
//        if (range != null) {
//            builder.add(BEGIN_FLAG).add(String.valueOf(begin));
//            builder.add(END_FLAG).add(String.valueOf(end));
//        }
//        return builder;
//    }
//
//    @Override
//    public List<LamiResultTable> execute(ITmfTrace trace, @Nullable TmfTimeRange timeRange,
//            String extraParamsString, IProgressMonitor monitor) throws CoreException {
//        /* Should have been called already, but in case it was not */
//        initialize();
//
//        final @NonNull String baseCommand = String.join(SPACE, getBaseCommand(timeRange).build());
//        final @NonNull String trimmedExtraParams = requireNonNull(extraParamsString.trim());
//        final @NonNull String tracePath = DOUBLE_QUOTES + requireNonNull(trace.getPath()) + DOUBLE_QUOTES;
//
//        @NonNull String fullCommand = String.join(SPACE, baseCommand, trimmedExtraParams, tracePath);
//
//        LOGGER.info(() -> "[LamiAnalysis:RunningExecuteCommand] " + fullCommand); //$NON-NLS-1$
//        String output = getResultsFromCommand(fullCommand, monitor);
//
//        if (output.isEmpty()) {
//            IStatus status = new Status(IStatus.INFO, Activator.instance().getPluginId(), Messages.LamiAnalysis_NoResults);
//            throw new CoreException(status);
//        }
//
//        /*
//         * {
//         *   "results": [
//         *     {
//         *       "time-range": {
//         *         "type": "time-range",
//         *         "begin": 1444334398154194201,
//         *         "end": 1444334425194487548
//         *       },
//         *       "class": "syscall-latency",
//         *       "data": [
//         *         [
//         *           {"type": "syscall", "name": "open"},
//         *           45,
//         *           {"type": "duration", "value": 5562},
//         *           {"type": "duration", "value": 13835},
//         *           {"type": "duration", "value": 77683},
//         *           {"type": "duration", "value": 15263}
//         *         ],
//         *         [
//         *           {"type": "syscall", "name": "read"},
//         *           109,
//         *           {"type": "duration", "value": 316},
//         *           {"type": "duration", "value": 5774},
//         *           {"type": "duration", "value": 62569},
//         *           {"type": "duration", "value": 9277}
//         *         ]
//         *       ]
//         *     },
//         *     {
//         *       "time-range": {
//         *         "type": "time-range",
//         *         "begin": 1444334425194487549,
//         *         "end": 1444334425254887190
//         *       },
//         *       "class": "syscall-latency",
//         *       "data": [
//         *         [
//         *           {"type": "syscall", "name": "open"},
//         *           45,
//         *           {"type": "duration", "value": 1578},
//         *           {"type": "duration", "value": 16648},
//         *           {"type": "duration", "value": 15444},
//         *           {"type": "duration", "value": 68540}
//         *         ],
//         *         [
//         *           {"type": "syscall", "name": "read"},
//         *           109,
//         *           {"type": "duration", "value": 78},
//         *           {"type": "duration", "value": 1948},
//         *           {"type": "duration", "value": 11184},
//         *           {"type": "duration", "value": 94670}
//         *         ]
//         *       ]
//         *     }
//         *   ]
//         * }
//         *
//         */
//
//        ImmutableList.Builder<LamiResultTable> resultsBuilder = new ImmutableList.Builder<>();
//
//        try {
//            JSONObject obj = new JSONObject(output);
//            JSONArray results = obj.getJSONArray(LamiStrings.RESULTS);
//
//            if (results.length() == 0) {
//                /*
//                 * No results were reported. This may be normal, but warn the
//                 * user why a report won't be created.
//                 */
//                IStatus status = new Status(IStatus.INFO, Activator.instance().getPluginId(), Messages.LamiAnalysis_NoResults);
//                throw new CoreException(status);
//            }
//
//            for (int i = 0; i < results.length(); i++) {
//                JSONObject result = results.getJSONObject(i);
//
//                /* Parse the time-range */
//                JSONObject trObject = requireNonNull(result.getJSONObject(LamiStrings.TIME_RANGE));
//                LamiData trData = LamiData.createFromObject(trObject);
//                if (!(trData instanceof LamiTimeRange)) {
//                    throw new JSONException("Time range did not have expected class type."); //$NON-NLS-1$
//                }
//                LamiTimeRange tr = (LamiTimeRange) trData;
//
//                /* Parse the table's class */
//                LamiTableClass tableClass;
//                JSONObject tableClassObject = result.optJSONObject(LamiStrings.CLASS);
//                if (tableClassObject == null) {
//                    /*
//                     * "class" is just a standard string, indicating we use a
//                     * metadata-defined table class as-is
//                     */
//                    @NonNull String tableClassName = requireNonNull(result.getString(LamiStrings.CLASS));
//                    tableClass = getTableClassFromName(tableClassName);
//
//                    // FIXME Rest will become more generic eventually in the LAMI format.
//                } else if (tableClassObject.has(LamiStrings.INHERIT)) {
//                    /*
//                     * Dynamic title: We reuse an existing table class but
//                     * override the title.
//                     */
//                    String baseTableName = requireNonNull(tableClassObject.getString(LamiStrings.INHERIT));
//                    LamiTableClass baseTableClass = getTableClassFromName(baseTableName);
//                    String newTitle = requireNonNull(tableClassObject.getString(LamiStrings.TITLE));
//
//                    tableClass = new LamiTableClass(baseTableClass, newTitle);
//                } else {
//                    /*
//                     * Dynamic column descriptions: we implement a new table
//                     * class entirely.
//                     */
//                    String title = requireNonNull(tableClassObject.getString(LamiStrings.TITLE));
//                    JSONArray columnDescriptions = requireNonNull(tableClassObject.getJSONArray(LamiStrings.COLUMN_DESCRIPTIONS));
//                    List<LamiTableEntryAspect> aspects = getAspectsFromColumnDescriptions(columnDescriptions);
//
//                    tableClass = new LamiTableClass(nullToEmptyString(Messages.LamiAnalysis_DefaultDynamicTableName), title, aspects, Collections.EMPTY_SET);
//                }
//
//                /* Parse the "data", which is the array of rows */
//                JSONArray data = result.getJSONArray(LamiStrings.DATA);
//                ImmutableList.Builder<LamiTableEntry> dataBuilder = new ImmutableList.Builder<>();
//
//                for (int j = 0; j < data.length(); j++) {
//                    /* A row is an array of cells */
//                    JSONArray row = data.getJSONArray(j);
//                    ImmutableList.Builder<LamiData> rowBuilder = ImmutableList.builder();
//
//                    for (int k = 0; k < row.length(); k++) {
//                        Object cellObject = requireNonNull(row.get(k));
//                        LamiData cellValue = LamiData.createFromObject(cellObject);
//                        rowBuilder.add(cellValue);
//
//                    }
//                    dataBuilder.add(new LamiTableEntry(rowBuilder.build()));
//                }
//                resultsBuilder.add(new LamiResultTable(tr, tableClass, dataBuilder.build()));
//            }
//
//        } catch (JSONException e) {
//            LOGGER.severe(() -> "[LamiAnalysis:ErrorParsingExecutionOutput] msg=" + e.getMessage()); //$NON-NLS-1$
//            IStatus status = new Status(IStatus.ERROR, Activator.instance().getPluginId(), e.getMessage(), e);
//            throw new CoreException(status);
//        }
//
//        return resultsBuilder.build();
//    }
//
//    private LamiTableClass getTableClassFromName(String tableClassName) throws JSONException {
//        Map<String, LamiTableClass> map = requireNonNull(fTableClasses);
//        LamiTableClass tableClass = map.get(tableClassName);
//        if (tableClass == null) {
//            throw new JSONException("Table class " + tableClassName + //$NON-NLS-1$
//                    " was not declared in the metadata"); //$NON-NLS-1$
//        }
//        return tableClass;
//    }
//
//    /**
//     * Get the output of an external command, used for getting the metadata.
//     * Cannot be cancelled, and will not report errors, simply returns null if
//     * the process ended abnormally.
//     *
//     * @param command
//     *            The parameters of the command, passed to
//     *            {@link ProcessBuilder}
//     * @return The command output as a string
//     */
//    @VisibleForTesting
//    protected @Nullable String getOutputFromCommand(String command) {
//        LOGGER.info(() -> LOG_RUNNING_MESSAGE + ' ' + command.toString());
//
//        List<String> bashCommand = Arrays.asList(BASH_EXECUTABLE, BASH_FLAG, command);
//        List<String> lines = ProcessUtils.getOutputFromCommand(bashCommand);
//        if (lines == null) {
//            return null;
//        }
//        return String.join("", lines); //$NON-NLS-1$
//    }
//
//    /**
//     * Get the results of invoking the specified command.
//     *
//     * The result should start with '{"results":...', as specified by the LAMI
//     * JSON protocol. The JSON itself may be split over multiple lines.
//     *
//     * @param command
//     *            The command to run (program and its arguments)
//     * @param monitor
//     *            The progress monitor
//     * @return The analysis results
//     * @throws CoreException
//     *             If the command ended abnormally, and normal results were not
//     *             returned
//     */
//    @VisibleForTesting
//    protected String getResultsFromCommand(String command, IProgressMonitor monitor)
//            throws CoreException {
//
//        List<String> bashCommand = Arrays.asList(BASH_EXECUTABLE, BASH_FLAG, command);
//        List<String> lines = ProcessUtils.getOutputFromCommandCancellable(bashCommand,
//                monitor, nullToEmptyString(Messages.LamiAnalysis_MainTaskName), OUTPUT_READER);
//        return requireNonNull(String.join("", lines)); //$NON-NLS-1$
//    }
//
//    private static final OutputReaderFunction OUTPUT_READER = (reader, monitor) -> {
//        double workedSoFar = 0.0;
//
//        String line = reader.readLine();
//        while (line != null && !line.matches("\\s*\\{.*")) { //$NON-NLS-1$
//            /*
//             * This is a line indicating progress, it has the form:
//             *
//             * 0.123 3000 of 5000 events processed
//             *
//             * The first part indicates the estimated fraction (out of 1.0) of
//             * work done. The second part is status text.
//             */
//
//            // Trim the line first to make sure the first character is
//            // significant
//            line = line.trim();
//
//            // Split at the first space
//            String[] elems = line.split(" ", 2); //$NON-NLS-1$
//
//            if (elems[0].matches("\\d.*")) { //$NON-NLS-1$
//                // It looks like we have a progress indication
//                try {
//                    // Try parsing the number
//                    double cumulativeWork = Double.parseDouble(elems[0]) * 1000;
//                    double workedThisLoop = cumulativeWork - workedSoFar;
//
//                    // We're going backwards? Do not update the
//                    // monitor's value
//                    if (workedThisLoop > 0) {
//                        monitor.internalWorked(workedThisLoop);
//                        workedSoFar = cumulativeWork;
//                    }
//
//                    // There is a message: update the monitor's task name
//                    if (elems.length >= 2) {
//                        monitor.setTaskName(elems[1].trim());
//                    }
//                } catch (NumberFormatException e) {
//                    // Continue reading progress lines anyway
//                }
//            }
//
//            line = reader.readLine();
//        }
//
//        List<String> results = new ArrayList<>();
//        while (line != null) {
//            /*
//             * We have seen the first line containing a '{', this is our JSON
//             * output!
//             */
//            results.add(line);
//            line = reader.readLine();
//        }
//        return results;
//    };
//
//
//    @Override
//    public @NonNull String getName() {
//        return fName;
//    }
//
//    @Override
//    public boolean isUserDefined() {
//        return fIsUserDefined;
//    }
//
//}
